package org.beetl.online;

import org.beetl.core.*;
import org.beetl.core.debug.DebugContext;
import org.beetl.core.debug.DebugLockListener;
import org.beetl.core.exception.BeetlException;
import org.beetl.core.exception.ErrorInfo;
import org.beetl.core.resource.StringTemplateResourceLoader;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@RestController
public class OnlineDebugController {
	GroupTemplate debugGroupTemplate;
	GroupTemplate groupTemplate;
	ThreadPoolExecutor executor = new ThreadPoolExecutor(1,10,1, TimeUnit.MINUTES,new ArrayBlockingQueue<>(100));

	StringTemplateResourceLoader loader = new StringTemplateResourceLoader();
	@PostMapping("/debug/run")
	public Map render(String json, String input, HttpServletRequest request){
		if(input.length()>250||json.length()>100){
			return getConsole("输入内容过长");
		}
		//删除之前的脚本
		Script oldScript = (Script)request.getSession(true).getAttribute("script");
		if(oldScript!=null){
			DebugContext debugContext = (DebugContext)oldScript.getContext();
			debugContext.setDebugStatus(2);
			//运行旧的脚本，释放工作线程池
			debugContext.stepToFinish();
			request.getSession(true).removeAttribute("script");
			request.getSession(true).removeAttribute("debugInfo");

		}
		//输入变量
		Map jsonParas = new HashMap();
		if(json!=null){
			Script script = debugGroupTemplate.getScript(json,loader);
			BeetlException beetlException = script.validate();
			if(beetlException!=null){
				ErrorInfo errorInfo = new ErrorInfo(beetlException);
				return getConsole("执行参数脚本校验出错:"+getErrorMsg(errorInfo)+" @line "+errorInfo.getErrorTokenLine());
			}
			DebugContext debugContext = (DebugContext)script.getContext();
			//忽略所有断点和单行调试，直接运行结束
			debugContext.setDebugStatus(2);
			script.execute();
			if(!script.isSuccess()){
				return getConsole("执行参数脚本运行出错:"+getErrorMsg(script.getErrorInfo())+" @line "+script.getErrorInfo().getErrorTokenLine());
			}
			jsonParas = script.getResult();
		}

		if(input==null||input.trim().length()==0) {
			getConsole("需要输入运行脚本");
		}

		//脚本执行
		Script script = debugGroupTemplate.getScript(input,loader);
		BeetlException exception = script.validate();
		if(exception!=null){
			ErrorInfo errorInfo = new ErrorInfo(exception);
			return getConsole("脚本校验出错:"+errorInfo.getMsg()+" @line "+errorInfo.getErrorTokenLine());
		}
		DebugContext debugContext = (DebugContext)script.getContext();
		//默认单行调试开始
		debugContext.setDebugStatus(0);
		Map debugInfo = new HashMap();
		debugInfo.put("line",-1);
		debugInfo.put("vars", null);
		request.getSession(true).setAttribute("debugInfo",debugInfo);
		debugContext.setDebugLockListener(new DebugLockListener() {
			@Override
			public void notifyHoldon(DebugContext debugContext,int line) {
				//执行到断点
				debugInfo.put("line",line);
				debugInfo.put("vars",debugContext.currentVars(line));
				System.out.println("wait on line "+line+"  vars "+debugContext.currentVars(line));;
			}
		});
		script.binding(jsonParas);
		try{
			executor.execute(new Runnable() {
				@Override
				public void run() {
					script.execute();
					if(!script.isSuccess()){
						debugInfo.put("msg",script.getErrorInfo().toString());
					}
				}
			});
		}catch (Exception e){
			e.printStackTrace();
			return getConsole("服务器负荷满，请稍后再试");
		}

		sleep();
		request.getSession(true).setAttribute("script",script);
		return getResponse("运行到 "+debugContext.getDebugLock().getCurrentLine()+"行",debugInfo);

	}

	//运行脚本
	@PostMapping("/debug/runScript")
	public Map runScript(String json, String input, HttpServletRequest request){
		if(input.length()>250||json.length()>100){
			return getConsole("输入内容过长");
		}

		Map jsonParas = null;
		if(json!=null){
			Script script = groupTemplate.getScript(json,loader);
			BeetlException beetlException = script.validate();
			if(beetlException!=null){
				ErrorInfo errorInfo = new ErrorInfo(beetlException);
				return getConsole("执行参数脚本校验出错:"+getErrorMsg(errorInfo)+" @line "+errorInfo.getErrorTokenLine());
			}
			script.execute();
			if(!script.isSuccess()){
				return getConsole("执行参数脚本运行出错:"+getErrorMsg(script.getErrorInfo())+" @line "+script.getErrorInfo().getErrorTokenLine());
			}
			jsonParas = script.getResult();
		}

		Map ret = null;
		double time=0;
		if(input!=null){
			long start = System.nanoTime();
			Script script = groupTemplate.getScript(input,loader);
			BeetlException beetlException = script.validate();
			if(beetlException!=null){
				ErrorInfo errorInfo = new ErrorInfo(beetlException);
				return getConsole("脚本校验出错:"+getErrorMsg(errorInfo)+" @line "+errorInfo.getErrorTokenLine());
			}

			script.binding(jsonParas);
			script.execute();
			if(!script.isSuccess()){
				return getConsole("执行脚本运行出错:"+getErrorMsg(script.getErrorInfo())+" @line "+script.getErrorInfo().getErrorTokenLine());
			}
			ret = script.getResult();
			long end = System.nanoTime();
			time = ((double)(end-start))/(1000*1000);
		}

		return getConsole("执行时间:"+time+"毫秒 执行结果 "+ret);


	}

	@PostMapping("/debug/validate")
	public Map validate( String input,HttpServletRequest request){

		Script script = groupTemplate.getScript(input,loader);
		BeetlException exception = script.validate();
		if(exception!=null){
			ErrorInfo errorInfo = new ErrorInfo(exception);
			return getConsole("脚本校验出错:"+getErrorMsg(errorInfo));
		}
		Map<String, Integer> levelOneMap = script.program.metaData.getTemplateRootScopeIndexMap();
		Map<String, Integer> globalIndexMap = script.program.metaData.globalIndexMap;
		Map<String, String[]> globalAttrs = script.program.metaData.globalVarAttr;
		return getConsole("脚本校验成功:"+"需要外部输入的变量是 "+ globalIndexMap.keySet()+",脚本计算的变量有 "+levelOneMap.keySet());
	}
	@PostMapping("/debug/stepOneLine")
	public Map step(HttpServletRequest request){
		Script script = (Script)request.getSession(true).getAttribute("script");
		Map debugInfo = (Map)request.getSession(true).getAttribute("debugInfo");
		if(script==null||debugInfo==null){
			return getConsole("无可Debug的脚本，可能会话过期，请先运行脚本");
		}
		if(script.isDone()){
			debugInfo.put("line",0);
			return getResponse("脚本运行已经结束 ",debugInfo);
		}
		DebugContext debugContext = (DebugContext)script.getContext();
		debugInfo = (Map)request.getSession(true).getAttribute("debugInfo");
		debugContext.stepOnLine();
		sleep();
		return getResponse("运行到 "+debugContext.getDebugLock().getCurrentLine()+"行",debugInfo);

	}

	@PostMapping("/debug/stepToFinish")
	public Map stepToFinish(HttpServletRequest request){
		Script script = (Script)request.getSession(true).getAttribute("script");
		Map debugInfo = (Map)request.getSession(true).getAttribute("debugInfo");
		if(script==null||debugInfo==null){

			return getConsole("无可Debug的脚本，可能会话过期，请先运行脚本");
		}
		if(script.isDone()){
			debugInfo.put("line",0);
			return getResponse("脚本运行已经结束 ",debugInfo);
		}
		DebugContext debugContext = (DebugContext)script.getContext();
		debugInfo = (Map)request.getSession(true).getAttribute("debugInfo");
		debugContext.stepToFinish();
		sleep();

		Map result = script.getResult();
		debugInfo.put("line",0);
		debugInfo.put("vars", result);
		return getResponse("运行结束,输出最后结果",debugInfo);

	}

	/**
	 * 在线体验简单的只允许设置一个断点
	 * @param line
	 * @param request
	 * @return
	 */
	@PostMapping("/debug/setBreakline")
	public Map stepToFinish(int line,HttpServletRequest request){
		Script script = (Script)request.getSession(true).getAttribute("script");
		Map debugInfo = (Map)request.getSession(true).getAttribute("debugInfo");
		if(script==null||debugInfo==null){
			return getConsole("无可Debug的脚本，可能会话过期，请先运行脚本");
		}
		if(script.isDone()){
			return getConsole("脚本运行已经结束 ");
		}
		DebugContext debugContext = (DebugContext)script.getContext();
		debugContext.getBreakLines().clear();
		debugContext.getBreakLines().add(line);
		debugInfo = (Map)request.getSession(true).getAttribute("debugInfo");

		return getResponse("下个断点设置到"+line+"行,beetl允许设置多个断点，在线体验支持设置1个",debugInfo);

	}

	@PostMapping("/debug/stepToBreak")
	public Map stepToBreak(HttpServletRequest request){
		Script script = (Script)request.getSession(true).getAttribute("script");
		Map debugInfo = (Map)request.getSession(true).getAttribute("debugInfo");
		if(script==null||debugInfo==null){
			return getConsole("无可Debug的脚本，可能会话过期，请先运行脚本");
		}
		if(script.isDone()){
			return getConsole("脚本运行已经结束 ");
		}
		DebugContext debugContext = (DebugContext)script.getContext();
		debugInfo = (Map)request.getSession(true).getAttribute("debugInfo");
		debugContext.stepNextToBreakLine();
		sleep();
		return getResponse("运行到 "+debugContext.getDebugLock().getCurrentLine()+"行",debugInfo);

	}



	protected String getErrorMsg(ErrorInfo errorInfo){
		return errorInfo.toString();
	}

	protected void sleep(){
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}

	protected Map getConsole(String msg){
		Map map = new HashMap();
		map.put("console",msg);
		map.put("debug","");
		return map;
	}


	protected Map getResponse(String msg,Map debugInfo){
		Map map = new HashMap();
		map.put("console",msg);
		map.put("debug",debugInfo);
		return map;
	}


	@PostConstruct
	protected  void init() throws IOException {


		Configuration conf = Configuration.defaultConfiguration();
		debugGroupTemplate = new GroupTemplate(conf);
		//Debug
		debugGroupTemplate.setEngine(new org.beetl.core.debug.OnlineDebugTemplateEngine());
		debugGroupTemplate.setErrorHandler(new ReThrowConsoleErrorHandler());
		debugGroupTemplate.setNativeSecurity(new WhiteListNativeSecurityManager());

		//不包含debug信息
		Configuration conf2 = Configuration.defaultConfiguration();
		groupTemplate = new GroupTemplate(conf2);
		groupTemplate.setErrorHandler(new ReThrowConsoleErrorHandler());
		groupTemplate.setNativeSecurity(new WhiteListNativeSecurityManager());
	}
}
